/**
 * @author 14544
 * @date 2020/4/2 11:04
 */

// 类型兼容性

// 当一个类型y可以正确赋值给类型x，那我们可以说x兼容y。
// TypeScript结构化类型系统的基本规则是，如果x要兼容y，那么y至少具有与x相同的属性。（注意是：y上至少有x相同的属性）
{
    // 声明一个接口具有 name 属性
    interface Named {
        name:string
    }

    // 声明一个类 具有 name、age属性
    class Animal {
        constructor(public name:string, public age:number) {}
    }

    // 定义个猫属于 Named 类型
    let cat:Named = {
        name:'tuFei'
    };
    // 定义个狗属于 Animal 类型
    let dog = new Animal('huang',22);
    console.log(cat,dog)

    // 将dog类型赋给 cat类型，没有报错表示cat类型兼容dog，因为dog类型中具有与cat相同的name属性， 编译器检查cat中的每个属性，看是否能在dog中也找到对应属性。
    cat = dog
    console.log(cat,dog) // 现在两个都是 Animal的实例类型

    // 检查函数参数时使用相同的规则：
    function greet(n:Named) { // 这里定义参数类型是Named
        // 注意，dog有个额外的age属性，但这不会引发错误。 只有目标类型（这里是Named）的成员会被一一检查是否兼容。
        console.log(`Hello,${n.name}`)
    }
    greet(dog) // 传入的类型是Animal类型的实例
}

// 比较两个函数的参数是否兼容
{
    // 定义两个函数的参数类型不同，但返回值类型相同
    type Dog = (name:string,age:number)=>number;
    type Cat = (name:string)=>number;

    // dog 函数返回age
    let dog:Dog = (name,age)=> age;

    // cat 函数返回 100
    let cat:Cat = (name)=> 100;

    // cat类型可以赋给dog类型，
    // 因为查看x是否能赋值给y，首先看它们的参数列表。 x的每个参数必须能在y里找到对应类型的参数。 注意的是参数的名字相同与否无所谓，只看它们的类型。 这里，x的每个参数在y中都能找到对应的参数，所以允许赋值。
    dog = cat;
    // console.log(dog('bigDog'))// 返回的是100 表示类型更改成功

    // dog类型不能赋给cat，因为dog的所有的参数类型，不能在cat中找到，例如：age
    // cat = dog;
    console.log(cat('smallCat')) // 返回的依然是100，所以类型没有发生改变

    // 因此对于函数之间参数兼容来说，参数多的兼容参数少的
}

// 比较两个函数的返回值的兼容
{
    // 定义一个函数，返回一个对象，对象中包含name、age、other属性
    let dog = (name:string,age:number) => ({
        name,
        age,
        other:'Hello,Dog!'
    })

    // 定义一个函数，返回一个对象，对象中包含name、age属性
    let cat = (name:string,age:number) => ({
        name,
        age
    })

    // cat类型不能正确赋给dog类型，因为dog函数中返回other属性在cat的返回值中找不到
    // dog = cat;
    console.log(dog('xiaoShu',22))

    cat = dog;
    console.log(cat('LiHao',45)) // 返回了other属性，表示类型兼容成功

    // 说明结构之间的兼容成员少的兼容成员多的
}
// 剩余参数与必须参数、可选参数的兼容
{
    function invokeLater1(args: any[], callback: (...args: any[]) => void) {
        /* ... Invoke callback with 'args' ... */
        callback(...args)
    }

    invokeLater1([1, 2], (x, y) => console.log(x + ', ' + y));

    invokeLater1([10, 10], (x?, y?) => console.log(x + ', ' + y));
    // 可以看出 剩余参数兼容必须参数、可选参数的
}
// 必须参数与剩余参数、可选参数的兼容
{
    function invokeLater2(args: any[], callback: (x:number,y:number) => void) {
        /* ... Invoke callback with 'args' ... */
        // callback(...args)
    }

    invokeLater2([3, 2], (x?, y?) => console.log(x + ', ' + y));

    invokeLater2([110, 10], (...args:any[]) => console.log(args));

    // 可以看出 必须参数兼容剩余参数、可选参数的
}
// 可选参数与必须参数、剩余参数的兼容
{
    let a = (x:number,y:number)=>{}  // 必须参数
    let b = (x?:number,y?:number)=>{} // 可选参数
    let c = (...args:any[])=>{} // 剩余参数
    // b = a;
    // b = c;


    function invokeLater(args: any[], callback: (x?:number,y?:number) => void) {
        /* ... Invoke callback with 'args' ... */
        callback(...args)
    }

    // invokeLater([1, 29], (x:number, y:number) => console.log(x + ', ' + y));

    // invokeLater([50, 10], (...args:any[]) => console.log(args));

    // 可以看出 严格模式下可选参数不兼容必须参数、剩余参数的，非严格模式下互相兼容
}

// 函数的重载
// 对于有重载的函数，源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。
// 意思就是目标函数参数个数多于或者等于源函数参数个数
{
    function sayHello(name:string,age:number,grade:number,d:symbol):number // 目标函数
    function sayHello(name:string,age:number):string; // 目标函数
    function sayHello(name:any,sge:any):any {  // 实现的函数为源函数
        return name
    }
    // 相当于将源函数赋给目标函数，目标函数参数个数多于或者等于源函数参数个数
}

// 枚举
{
    // 枚举与数字之间可以互相兼容
    enum People {
        name,
        age,
        grade
    }

    // 枚举可以赋值给数字类型
    let number = 0
    number = People.name
    number = People.age
    console.log(number)

    // 数字类型可以赋值给枚举类型
    let otherNum:People.grade;
    otherNum = 10

    // 不同枚举间不可兼容
    enum Animal {
        name,
        age,
        grade
    }
    // 先定义一个People枚举类型，发现枚举间不不可兼容的
    // let peopleEnum = People.grade
    // peopleEnum = Animal.grade

}

// 类之间的兼容性 类与对象字面量和接口差不多
// 类的构造函数和静态成员不参与比较，
{
    class Animal {
        feet: number;
        constructor(name: string, numFeet: number) { }
    }

    class Size {
        feet: number;
        constructor(numFeet: number) { }
    }

    let a: Animal;
    let s: Size;

    a = s;  // OK
    s = a;  // OK
}
// 当类中有私有成员或受保护成员时，两个不同的类是不兼容，只有父类与子类可以互相兼容
{
    class Animal {
        feet: number;
        private age:number = 22 // 定义私有成员
        constructor(name: string, numFeet: number) { }
    }

    class Size {
        feet: number;
        constructor(numFeet: number) { }
    }

    let a: Animal;
    let s: Size;

    // a = s;  // error
    s = a;  // OK，结构化类型少的成员兼容多的成员

    // 定义个Animal的子类
    class Dog extends Animal {};
    let dog = new Dog('wm',22);
    a = dog;
    dog = a;
    // 父类与子类可以互相兼容
}

{
    class Animal {
        feet: number;
        protected age:number = 22  // 定义私有成员
        constructor(name: string, numFeet: number) { }
    }

    class Size {
        feet: number;
        constructor(numFeet: number) { }
    }

    let a: Animal;
    let s: Size;

    // a = s;  // error
    s = a;  // OK，少的兼容多的

    // 定义个Animal的子类
    class Dog extends Animal {};
    let dog = new Dog('wm',22);
    a = dog;
    dog = a;
    // 父类与子类可以互相兼容
}

// 泛型之间的兼容
{
    // 泛型接口
    interface People<T> {

    }

    interface Animal<U> {

    }

    let p:People<number>;
    let a:Animal<string>;
    p = a;
    a = p;
    // 可以互相兼容,对于没指定泛型类型的泛型参数时，会把所有泛型参数当成any比较。 然后用结果类型进行比较，就像上面第一个例子。
}

{
    // 泛型接口
    interface People<T> {
        name:T
    }

    interface Animal<U> {
        name:U
    }
    let p:People<number>;
    let a:Animal<string>;
    // p = a;
    // a = p;
    // 不可以互相兼容，指定了泛型类型的泛型参数时，发现类型不同，就不可以兼容了
}

{
    let p = function<T> (name:T,age:number):T {
        return name
    }

    let s = function<U> (name:U,age:number):U {
        return name
    }

    // 还没有指定类型参数时就可以互相兼容
    // 因为会把所有泛型参数当成any比较如： (x: any) => any matches (y: any) => any，所以能兼容
    let temp = p
    p=s;
    s=temp

    p<string>('dasd',22)
    s<number>(312,22)
}
